﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics.Contracts;

using Antlr.Runtime;
using Antlr.Runtime.Tree;

using Netium.Compiler.TypeSystem;
using Netium.Compiler.IntermediateRepresentation;
using Netium.Compiler.BackEnd.Interpreter;
using Netium.Compiler.BackEnd.Interpreter.RuntimeStack;

namespace Netium.Compiler.BackEnd.Interpreter
{
    public class SimpleInterpreter : IBackEndEngine
    {
        private readonly Dictionary<string, Action<CommonTree>> _statementExecutors;

        private ISymbolTable _symbolTable;
        private CommonTree _ir;
        private IStack _runtimeStack;

        public SimpleInterpreter(CommonTree tree, ISymbolTable symbolTable)
        {
            IR = tree;
            SymbolTable = symbolTable;

            _statementExecutors = new Dictionary<string, Action<CommonTree>>()
            {
                {"FUNC_BODY", ExecuteFuncBody},
                {"STAT_LIST", ExecuteStatList},
                {"FUNC_CALL", ExecuteFuncCall},
                {"LOOP", ExecuteLoop},
                {"TAIL_LOOP", ExecuteTailLoop},
                {"IF", ExecuteIf},
                {"STATEMENT", ExecuteStat}
            };
        }

        public void Execute()
        {
            SetupRuntimeEnvironment();
            CallEntryPoint();
        }

        private void SetupRuntimeEnvironment()
        {
            _runtimeStack = new RtStack();
        }

        private void CallEntryPoint()
        {
            var entryPoint = _symbolTable.Symbols.Where(s => s.Name.Equals("main", StringComparison.Ordinal) && s.Attributes.SymbolType == SymbolType.Function).FirstOrDefault();
            if (entryPoint == null)
                throw new Exception();

            ExecuteFunction(entryPoint);
        }

        private object ExecuteFunction(ISymbol functionId, params object[] arguments)
        {
            object returnVal = null;

            Prelude(functionId, arguments);

            try
            {
                var code = functionId.Attributes.Code;
                ExecuteFuncBody(code);
            }
            finally
            {
                returnVal = _runtimeStack.Pop()["$returnVal"].Value;
                Postlude();
            }

            return returnVal;
        }

        private void ExecuteFuncBody(CommonTree code)
        {
            Contract.Requires<ArgumentException>(code.Text.Equals("FUNC_BODY", StringComparison.Ordinal));

            var statements = code.Children;

            foreach (var stat in statements)
            {
                ExcuteStatement(stat);
            }
        }

        private void Postlude()
        {
            _runtimeStack.Pop();
        }

        private void Prelude(ISymbol functionId, object[] arguments)
        {
            // Get function scope symbol table
            ISymbolTable symTable = functionId.Attributes.SymbolTable;

            // Initialize a new stack frame for the function
            var stackFrame = _runtimeStack.PushNew();

            // Allocate the slot for the return value;
            var slot = stackFrame.AllocateSlot("$returnVal");
            slot.Symbol = functionId;
            slot.Value = null;

            // Allocate the slot for the arguments;
            var paramSymbols = symTable.Symbols.Where(s => s.Attributes.SymbolType == SymbolType.Parameter).ToArray();
            if (paramSymbols.Length != arguments.Length)
                throw new Exception();
            for (int i = 0; i < paramSymbols.Length; i++)
            {
                slot = stackFrame.AllocateSlot(paramSymbols[i].Name);
                slot.Symbol = paramSymbols[i];
                slot.Value = ConvertValue(slot.Symbol.Attributes.Type, arguments[i]);
            }

            // Allocate the slot for the local variables
            var localVariableSymbols = symTable.Symbols.Where(s => s.Attributes.SymbolType == SymbolType.Variable);
            foreach (var localVariableSymbol in localVariableSymbols)
            {
                slot = stackFrame.AllocateSlot(localVariableSymbol.Name);
                slot.Symbol = localVariableSymbol;
                slot.Value = null;
            }
        }

        private void ExcuteStatement(ITree stat)
        {
            throw new NotImplementedException();
        }

        private object ConvertValue(IDataType type, object value)
        {
            dynamic val = value;
            if (type == PrimitiveDataTypes.IntType)
                return (int)val;
            else if (type == PrimitiveDataTypes.CharType)
                return (char)val;
            else if (type == PrimitiveDataTypes.ShortType)
                return (short)val;
            else if (type == PrimitiveDataTypes.LongType)
                return (long)val;
            else if (type == PrimitiveDataTypes.FloatType)
                return (float)val;
            else if (type == PrimitiveDataTypes.DoubleType)
                return (double)val;
            else if (type == PrimitiveDataTypes.VoidType)
                return null;
            else
                throw new NotImplementedException();
        }

        public ISymbolTable SymbolTable
        {
            get { return _symbolTable; }
            private set { Contract.Requires<ArgumentNullException>(value != null); _symbolTable = value; }
        }

        public CommonTree IR
        {
            get { return _ir; }
            private set { Contract.Requires<ArgumentNullException>(value != null); _ir = value; }
        }
    }
}
